home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Communication / NewsBase / Source / INntpIO.m < prev    next >
Text File  |  1993-01-12  |  41KB  |  1,294 lines

  1. /*$Copyright:
  2.  * Copyright (C) 1992.5.22. Recruit Co.,Ltd. 
  3.  * Institute for Supercomputing Research
  4.  * All rights reserved.
  5.  * NewsBase  by ISR, Kazuto MIYAI, Gary ARAKAKI, Katsunori SUZUKI, Kok-meng Lue
  6.  *
  7.  * You may freely copy, distribute and reuse the code in this program under 
  8.  * following conditions.
  9.  * - to include this notice in the source code, if it is to be distributed 
  10.  *   with source code.
  11.  * - to add the file named "COPYING" within the code, which shall include 
  12.  *   GNU GENERAL PUBLIC LICENSE(*).
  13.  * - to display an acknowledgement in binary code as follows: "This product
  14.  *   includes software developed by Recruit Co.,Ltd., ISR."
  15.  * - to display a notice which shall state that the users may freely copy,
  16.  *   distribute and reuse the code in this program under GNU GENERAL PUBLIC
  17.  *   LICENSE(*)
  18.  * - to indicate the way to access the copy of GNU GENERAL PUBLIC LICENSE(*)
  19.  *
  20.  *   (*)GNU GENERAL PUBLIC LICENSE is stored in the file named COPYING
  21.  * 
  22.  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
  23.  * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
  24.  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  25. $*/
  26.  
  27. #import "IOrderedListD.h"
  28. #import "InfoD.h"
  29. #import "INewsgroupInfoD.h"
  30. #import "IOrderedListD.h"
  31. #import "INntpIO.h"
  32. #import "ITreeNodeD.h"
  33. #import "errdebug.h"
  34. #import "data_types.h"
  35. #import "IUifNode.h"
  36.  
  37. #import <appkit/appkit.h>
  38. #import <stdio.h>
  39. #import <ctype.h>
  40. #import <libc.h>
  41. #import <string.h>
  42. #import <mach/mach.h>
  43. #import <objc/zone.h>
  44. #import "Localization.h"
  45.  
  46. #define LoStr(key)      doLocalString(NULL,key,NULL)
  47.  
  48. static HashTable *tableOfNoiseWords;
  49. static const char *noiseWords[] = {
  50. #include "noise_words.h"
  51. };
  52.  
  53. #define        SIZEMARKONE    "sizemarkOne"
  54. #define        SIZEMARKTWO    "sizemarkTwo"
  55. #define        SIZEMARKTHREE    "sizemarkThree"
  56.  
  57. static const char *ikey[MAX_NO_OF_TOKENS];
  58.  
  59. @implementation INntpIO
  60.  
  61. + initialize
  62. {
  63.     char filename[512];
  64.     NXStream *stream;
  65.     char *buffer;
  66.     int len, maxlen;
  67.     char *ptr;
  68.     const char **word;
  69.  
  70.     tableOfNoiseWords = [[HashTable alloc] initKeyDesc:"*"];
  71.     sprintf(filename, "%.491s/.NewsBaseNoiseWords", getenv("HOME"));
  72.     if ((stream = NXMapFile(filename, NX_READWRITE)) != NULL) {
  73.         NXSeek(stream, (long)0, NX_FROMEND);
  74.         NXPutc(stream, '\0');
  75.         NXGetMemoryBuffer(stream, &buffer, &len, &maxlen);
  76.         for (ptr = strtok(buffer, " \n\r"); ptr != NULL;
  77.             ptr = strtok(NULL, " \n\r")) {
  78.             [tableOfNoiseWords insertKey:ptr value:nil];
  79.         }
  80.         NXCloseMemory(stream, NX_SAVEBUFFER);
  81.         return(self);
  82.     } else {
  83.         for (word = noiseWords; *word != NULL; ++word) {
  84.             [tableOfNoiseWords insertKey:*word value:nil];
  85.         }
  86.         return(self);
  87.     }
  88. }
  89.  
  90. - initServer:(char *)nntpHost allNews:(BOOL)allNewsFlag
  91.             newsGroupMode:(BrowserMode)g_mode
  92.             articleMode:(BrowserMode)a_mode
  93. {
  94.     const char    *buffer;
  95.  
  96.     iNewsGroupMode = g_mode;
  97.     iArticleMode = a_mode;
  98.     irFlag = UNMARKEDONLY;
  99.  
  100.     // set default
  101.     iBoundaryForTwo = 100; iBoundaryForThree = 300;
  102.     // read default database and set boundary Line # for line mark
  103.     if ((buffer = NXGetDefaultValue(OWNER, ARTICLESIZEMARK)) != 0) {
  104.         sscanf(buffer, "0:%d:%d", &iBoundaryForTwo, &iBoundaryForThree);
  105.     }
  106.     DBG(1, fprintf(stderr,"boundaryTwo = %d\t Tree = %d\n", 
  107.                         iBoundaryForTwo, iBoundaryForThree));
  108.     return [super initServer:nntpHost allNews:allNewsFlag];
  109. }
  110.  
  111. - subDirectoryOf:knode
  112. {
  113.     /* directory structure of newsgroup tree                */
  114.     /*                                    */
  115.     /*  [iNewsGroupTreeRoot]-+-[tree node]-+-[newsgroupInfo]        */
  116.     /*                 |             +-[tree node]-[newsgroupInfo]*/
  117.     /*                       +-[tree node]+[tree node]    */
  118.     
  119.     if (iNewsGroupMode == Tree) {
  120.     return [self _subDirectoryOf:knode];
  121.     } else {
  122.     return [self _flatDirectoryOf:knode];
  123.     }
  124. }
  125.  
  126. - _subDirectoryOf:knode
  127. {
  128.     id        dirNewsGroup, childNewsGroup;
  129.     id        listnode;
  130.     id        node;
  131.     id        n;
  132.     int        i, j;
  133.     int        count;
  134.     char    *subscribe;
  135.     id        groupInfo;
  136.     char    *newsgroupName;
  137.     NXTreeState    state;
  138.     id        tnode;
  139.  
  140.     /* set newsgroup Tree node */
  141.     if (knode == nil) {
  142.     /* dirNode is rootNode */
  143.     dirNewsGroup = iNewsGroupTreeRoot;
  144.     } else {
  145.     dirNewsGroup = [knode linkedData];
  146.     }
  147.     
  148.     listnode = [[IOrderedListD allocFromZone:[self zone]]
  149.                         initWithKey:"listnode"];
  150.     
  151.     for (i=0; childNewsGroup=[dirNewsGroup objectAt:i]; ++i) {
  152.     if ([childNewsGroup isMemberOf:[ITreeNodeD class]]==NO) {
  153.         /* childNewsGroup is not a real newsgroup Tree node */
  154.         continue;
  155.     }
  156.     
  157.     // set leaf or not
  158.     count = 0;
  159.     for (j=0; n=[childNewsGroup objectAt:j]; ++j) {
  160.         if ([n isMemberOf:[ITreeNodeD class]]==NO) {
  161.         continue;
  162.             }
  163.             ++count;
  164.     }
  165.     
  166.     // a childNewsGroup is classified into 3 types
  167.     //   1. directory
  168.     //   2. leaf and directory
  169.     //        childNewsGroup has tree node and newsgroup info
  170.     //   3. leaf
  171.     
  172.     // 1. directory only
  173.     if ((count > 0) && [childNewsGroup dataForKey:GROUPINFO] == nil) {
  174.         node = [[IUifNode allocFromZone:[self zone]] 
  175.                         initWithKey:[childNewsGroup key]];
  176.         [listnode addObject:node];
  177.         [node setTitleForCell:[childNewsGroup key]];
  178.         [node setLinkedData:childNewsGroup];
  179.             [node setNodeType:DirOfSubDirs];
  180.         [node setLeaf:NO];
  181.         
  182.         // check newsgroup is subscribed or not here to 
  183.         // set directory active or notactive
  184.         [node setActive:NO];    // default is not active
  185.  
  186.         [childNewsGroup initState:&state];
  187.         while (tnode = [childNewsGroup nextState:&state]) {
  188.         if ((strcmp([tnode key], GROUPINFO) == 0)
  189.             && (strcmp([tnode infoForKey:SUBSCRIBE], "yes") == 0 )) {
  190.             // found a active newsgroup under me
  191.             [node setActive:YES];
  192.             break;
  193.         }
  194.         }
  195.     } else if (count > 0) {
  196.     // 2. leaf and directory
  197.     
  198.         // insert leaf cell
  199.         // 
  200.         node = [[IUifNode allocFromZone:[self zone]] 
  201.                     initWithKey:[childNewsGroup key]];
  202.         [listnode addObject:node];
  203.         [node setTitleForCell:[childNewsGroup key]];
  204.         [node setLinkedData:childNewsGroup nodeType:DirOfItems];
  205.         [node setLeaf:YES];
  206.         // set image
  207.         if ((groupInfo=[childNewsGroup dataForKey:GROUPINFO]) != nil) {
  208.         if ([groupInfo isAllArticleMarked] == NO) {
  209.             [node setImageForCell:
  210.                     [NXImage findImageNamed:UNREADMARK]];
  211.         }
  212.         }
  213.         /* set newsgroup name for dataGroupName */
  214.         [node setDataGroupName:[[childNewsGroup dataForKey:GROUPINFO]
  215.         infoForKey:GROUPNAME]];
  216.  
  217.         DBG(10,fprintf(stderr,"newsgroupName=%s",newsgroupName));
  218.         /* set node active for subscribe, no active for unsubscribe */
  219.         subscribe = [[childNewsGroup dataForKey:GROUPINFO]
  220.                             infoForKey:SUBSCRIBE];
  221.         if(strncmp(subscribe,"y",1)==0) {
  222.         [node setActive:YES];
  223.         } else {
  224.         [node setActive:NO];
  225.         }
  226.  
  227.         // insert directory cell
  228.         //
  229.         node = [[IUifNode allocFromZone:[self zone]] 
  230.                         initWithKey:[childNewsGroup key]];
  231.         [listnode addObject:node];
  232.         [node setTitleForCell:[childNewsGroup key]];
  233.         [node setLinkedData:childNewsGroup];
  234.             [node setNodeType:DirOfSubDirs];
  235.         [node setLeaf:NO];
  236.  
  237.         // check newsgroup is subscribed or not here to 
  238.         // set directory active or notactive
  239.         [node setActive:NO];    // default is not active
  240.  
  241.         [childNewsGroup initState:&state];
  242.         while (tnode = [childNewsGroup nextState:&state]) {
  243.         if ((strcmp([tnode key], GROUPINFO) == 0)
  244.             && (strcmp([tnode infoForKey:SUBSCRIBE], "yes") == 0 )) {
  245.             [node setActive:YES];
  246.             break;
  247.         }
  248.         }
  249.     } else {
  250.     // 3. leaf
  251.  
  252.         node = [[IUifNode allocFromZone:[self zone]] 
  253.                         initWithKey:[childNewsGroup key]];
  254.         [listnode addObject:node];
  255.         [node setTitleForCell:[childNewsGroup key]];
  256.         [node setLinkedData:childNewsGroup];
  257.             [node setNodeType:DirOfItems];
  258.         [node setLeaf:YES];
  259.         if ((groupInfo=[childNewsGroup dataForKey:GROUPINFO]) != nil) {
  260.         if ([groupInfo isAllArticleMarked] == NO) {
  261.             [node setImageForCell:[NXImage findImageNamed:UNREADMARK]];
  262.         }
  263.         }
  264.         [node setDataGroupName:[[childNewsGroup dataForKey:GROUPINFO] 
  265.                 infoForKey:GROUPNAME]];
  266.  
  267.         subscribe = [[childNewsGroup dataForKey:GROUPINFO]
  268.                                  infoForKey:SUBSCRIBE];
  269.             if(strncmp(subscribe,"y",1)==0) {
  270.         [node setActive:YES];
  271.         } else {
  272.         [node setActive:NO];
  273.         }
  274.     }
  275.     }
  276.     return listnode;
  277. }
  278.  
  279. - _flatDirectoryOf:knode
  280. {
  281.     id        newsGroup;
  282.     id        listnode, node;
  283.     NXTreeState state;
  284. //    const char    *key;
  285.     char    *subscribe;
  286.     id        treenode;
  287.     char    *newsgroupName;
  288.     id        groupInfo;
  289.  
  290.     /* set newsgroup Tree node */
  291.     /* only rebuild from root(==nil) for now */
  292.     if (knode == nil) {
  293.     /* dirNode is rootNode */
  294.     newsGroup = iNewsGroupTreeRoot;
  295.     } else {
  296.     newsGroup = [knode linkedData];
  297.     }
  298.     
  299.     listnode = [[IOrderedListD allocFromZone:[self zone]]
  300.                         initWithKey:"listnode"];
  301.     [newsGroup initState:&state];
  302.     while (treenode=[newsGroup nextState:&state]) {
  303.     if ([treenode isMemberOf:[ITreeNodeD class]]==NO) {
  304.         /* newsGroup is not a real newsgroup Tree node */
  305.         continue;
  306.     }
  307.     if ([treenode dataForKey:GROUPINFO]==nil) {
  308.         /* this node is only for grouping node, not have newsgroup inof */
  309.         continue;
  310.     }
  311.         newsGroup = treenode;
  312.  
  313.     newsgroupName = [[newsGroup dataForKey:GROUPINFO] infoForKey:GROUPNAME];
  314.     node = [[IUifNode allocFromZone:[self zone]] initWithKey:newsgroupName];
  315.     [node setTitleForCell:newsgroupName];
  316.     [node setLinkedData:newsGroup nodeType:DirOfItems];
  317.     [node setDataGroupName:newsgroupName];
  318.     [node setLeaf:YES];        /* flat's cell is always leaf */
  319.     // set image
  320.     if ((groupInfo=[newsGroup dataForKey:GROUPINFO]) != nil) {
  321.         if ([groupInfo isAllArticleMarked] == NO) {
  322.         [node setImageForCell:[NXImage findImageNamed:UNREADMARK]];
  323.         }
  324.     }
  325.     subscribe = [[newsGroup dataForKey:GROUPINFO] infoForKey:SUBSCRIBE];
  326.     if(strncmp(subscribe,"y",1)==0) {
  327.         [node setActive:YES];
  328.     } else {
  329.         [node setActive:NO];
  330.     }
  331.     [listnode addObject:node];
  332.     }
  333.     return listnode;
  334. }
  335.  
  336. - itemHeadersOf:knode
  337. {
  338.     /* itemHeadersOf                             */
  339.     /*   from newsgroup browser: click on leaf cell of newsgroup browser */
  340.     /*   from article browser: click on article directory */
  341.     
  342.     id        linkdata;
  343.  
  344.     if ((linkdata = [knode linkedData]) == nil) {
  345.     return 0;
  346.     }
  347.  
  348.     switch ([knode nodeType]) {
  349.     case DirOfItems:
  350.     /* linkdata has groupInfo, so coming from NewsGroup browser */
  351.     switch(iArticleMode) {
  352.         case Flat:
  353.         /* get headers from newsgroup info */
  354.         return [self _itemHeadersOf:knode];
  355.         case Tree:
  356.         /* Tree mode */
  357.         /* clicked on newsgroup browser, create article reference tree */
  358.  
  359.         /* make iArticleDB with article headers */
  360.         [self _itemHeadersOf:knode];
  361.         /* make articleTree from iArticleDB */
  362.         [self _makeArticleTree];
  363.         /* return listnode for column-0 for ReferenceMode */
  364.         return [self _itemTreeHeadersOf:knode];
  365.         case BySubject:
  366.         /* return listnode for column-0 for BySubjectMode */
  367.         return([self _subjectHeadersOf:knode]);
  368.         case ByKeyword:
  369.         /* return listnode for column-0 for ByKeywordMode */
  370.         return([self _keywordHeadersOf:knode]);
  371.     }
  372.     case ReferenceGroup:
  373.     /* Article Browser should be in Tree Mode */
  374.         return [self _itemSubTreeHeadersOf:knode];
  375.     case SubjectGroup:
  376.     case KeywordGroup:
  377.     /* Article Browser should be in BySubject or ByKeyword Mode */
  378.         return([self _itemHeadersOfSubject:knode]);
  379.     default:
  380.         return(0);
  381.     }
  382. }
  383.  
  384. - _itemTreeHeadersOf:knode
  385. {
  386.     id        listnode, node;
  387.     int        i, j, k;
  388.     id        childArticle, articleItem, g_child, gg_child;
  389.     id        last_articleGroup;
  390.     char     *subject;
  391.     int        tree_node_count;
  392.     id        groupInfo, headerInfo;
  393.     const char    *newsgroupName;
  394.     char     *groupnameTmp;
  395.     char    *cline;
  396.     id        image;
  397.     BOOL    isUnreadArticle;
  398.  
  399.     /* _itemTreeHeadersOf is for filling cell in colum-0 of article browser */
  400.     /* _itemSubTreeHeadersOf is for column-1 */
  401.  
  402.     /* return listnode for column-0 */
  403.     listnode = [[IOrderedListD allocFromZone:[self zone]]
  404.                         initWithKey:"listnode"];
  405.                         
  406.     // search groupInfo
  407.     groupnameTmp = NXCopyStringBuffer([knode dataGroupName]);
  408.     makeTreeKey (groupnameTmp, ikey, ".");
  409.     groupInfo=[[iNewsGroupTreeRoot nodeForKey:ikey] 
  410.                         dataForKey:GROUPINFO];
  411.     free(groupnameTmp);
  412.     
  413.     /* iArticleReferenceTreeRoot sould be made befor here */
  414.     for (i=0;childArticle=[iArticleReferenceTreeRoot objectAt:i];++i) {
  415.     /*      iArticleReferenceTreeRoot strucrture            */
  416.     /* (1) case 1                            */
  417.     /*[root]--+-[tree_node]---[articleItem]-[header]        */
  418.     /*        |(childArticle)                    */
  419.     /* (2) case 2                            */
  420.     /*        +-[tree_node]-+-[articleItem]-[header]             */
  421.     /*      |        |                         */
  422.     /*      |        +-[tree_node]-[articleItem]-[header]    */
  423.     /*      |        +-[tree_node]-[articleItem]-[header]    */
  424.     /* (3) case 3                                 */
  425.     /*    (original article is already removed in server)         */
  426.     /*        +-[tree_node]-+                        */
  427.     /*            +-[tree_node]-[articleItem]-[header]    */
  428.     /*            +-[tree_node]-[articleItem]-[header]    */
  429.  
  430.     if ([childArticle isMemberOf:[ITreeNodeD class]]==NO) {
  431.         /* childArticle must be ITreeNodeD */
  432.         /* if you go into here, something wrong */
  433.         fprintf (stderr,"INntpIO: error: childArticle is not a member of ITreeNodeD\n");
  434.         continue;
  435.     }
  436.                         
  437.     /* search articleItem, count tree_node, set title for cell */
  438.     /* tree_node does not     */
  439.     /* have articleItem, use articleItem of last child of     */
  440.     /* this node as articleItem for tree_node        */
  441.     
  442.     articleItem = nil;        /* articleItem */
  443.     tree_node_count = 0;
  444.     isUnreadArticle = NO;
  445.     for (j=0; g_child=[childArticle objectAt:j]; ++j) {
  446.         if ([g_child isMemberOf:[IOrderedListD class]] &&
  447.             ([g_child dataForKey:HEADER_INFO]!=nil)) {
  448.         /* articleItem have header.tbl */
  449.         /* case (1) or (2) */
  450.         articleItem = g_child;
  451.         node = [[IUifNode allocFromZone:[self zone]] 
  452.                             initWithKey:[articleItem key]];
  453.         [listnode addObject:node];
  454.         [node setLeaf:YES];
  455.         [node setLinkedData:articleItem nodeType:Header];
  456.                 newsgroupName = [knode dataGroupName];
  457.                 [node setDataGroupName:newsgroupName];
  458.         headerInfo = [g_child dataForKey:HEADER_INFO];
  459.         subject = [headerInfo infoForKey:SUBJECT];
  460.         // set title and image
  461.         [node setTitleForCell:subject];
  462.         if ((cline=(char *)[headerInfo infoForKey:LINES]) != NULL) {
  463.             // article header has "Lines:", so set mark to cell
  464.             image = [self getArticleMarkFor:(int)atoi(cline)];
  465.             [node setImageForCell:image];
  466.         }
  467.  
  468.         /* check article is already read or not */
  469.         if ([groupInfo checkArticleIsRead:
  470.                 (int)[headerInfo infoForKey:ARTICLE_NUM]]) {
  471.             [node setActive:NO];
  472.         } else {
  473.             [node setActive:YES];
  474.         }
  475.         } else if ([g_child isMemberOf:[ITreeNodeD class]]==YES) {
  476.         /* g_child is tree_node */
  477.         /* case (2) or (3) */
  478.                 last_articleGroup = g_child;
  479.         ++ tree_node_count;
  480.         // check if this g_child's article is already read
  481.         for (k=0; gg_child=[last_articleGroup objectAt:k]; ++k) {
  482.             if ([gg_child isMemberOf:[IOrderedListD class]] &&
  483.               ((headerInfo=[gg_child dataForKey:HEADER_INFO])!=nil)) {
  484.             // gg_child is articleItem
  485.             if ([groupInfo checkArticleIsRead: 
  486.                 (int)[headerInfo infoForKey:ARTICLE_NUM]] == NO) {
  487.                 // there is an unread article 
  488.                 isUnreadArticle = YES;
  489.                 break;
  490.             }
  491.             }
  492.         }        
  493.         }
  494.     }
  495.     if (articleItem == nil) {
  496.         /* case (3), can't find right article */
  497.         /* g_child is a last article of this article group */
  498.         for (j=0; gg_child=[last_articleGroup objectAt:j]; ++j) {
  499.         if ([gg_child isMemberOf:[IOrderedListD class]] &&
  500.             ([gg_child dataForKey:HEADER_INFO]!=nil)) {
  501.             articleItem = gg_child;
  502.             break;
  503.         }
  504.         }
  505.     }
  506.     subject = [[articleItem dataForKey:HEADER_INFO] infoForKey:SUBJECT];
  507.         
  508.     if (tree_node_count >0 ) {
  509.         node = [[IUifNode allocFromZone:[self zone]] 
  510.                     initWithKey:[childArticle key]];
  511.         [listnode addObject:node];
  512.         [node setTitleForCell:subject];
  513.         [node setLeaf:NO];
  514.         [node setLinkedData:childArticle nodeType:ReferenceGroup];
  515.         [node setDataGroupName:[knode dataGroupName]];
  516.         // check if all article is read or not to set directory's active
  517.         if (isUnreadArticle == NO) {
  518.         // child node is all not active, so directory is not active
  519.         [node setActive:NO];
  520.         } else {
  521.         [node setActive:YES];
  522.         }
  523.     }
  524.     }
  525.     DBG(20, {int i; id child; for(i=0;child=[listnode objectAt:i];++i) {
  526.         fprintf(stderr,"child key=%s\n",[child key]);} });
  527.     return listnode;
  528. }
  529.         
  530. - _itemSubTreeHeadersOf:knode
  531. {
  532.     id        listnode, node;
  533.     id        articleGroup;
  534.     id        articleItem, headerInfo, groupInfo;
  535.     id        childArticle, g_child;
  536.     int        i, j;
  537.     const char    *newsgroupName;
  538. //    char    groupnameTmp[256];
  539.     char    *groupnameTmp;
  540.     char    *cline;
  541.     id        image;
  542.     
  543.     articleGroup = [knode linkedData];
  544.     /* articleGroup is [tree_node], represent article group node  */
  545.     
  546.     listnode = [[IOrderedListD allocFromZone:[self zone]]
  547.                              initWithKey:"listnode"];
  548.     for (i=0; childArticle=[articleGroup objectAt:i]; ++i) {
  549.     /*                (articleItem sould be displayed in    */
  550.     /*                        column-0)    */
  551.     /*        +-[tree_node]-+-([articleItem]-[header])             */
  552.     /*              |                         */
  553.     /*              +-[tree_node]-[articleItem]-[header]    */
  554.     /*              +-[tree_node]-[articleItem]-[header]    */
  555.  
  556.         if ([childArticle isMemberOf:[ITreeNodeD class]]==NO) {
  557.         /* only [tree_node] are under article group node */
  558.         continue;
  559.     }    
  560.     for (j=0; g_child=[childArticle objectAt:j]; ++j) {
  561.         if ([g_child isMemberOf:[IOrderedListD class]] && 
  562.             ((headerInfo=[g_child dataForKey:HEADER_INFO])!=nil)) {
  563.         /* g_child is article */
  564.         articleItem = g_child;
  565.         node = [[IUifNode allocFromZone:[self zone]] 
  566.                             initWithKey:[articleItem key]];
  567.         [listnode addObject:node];
  568.         [node setTitleForCell:[headerInfo infoForKey:MESSAGE_ID]];
  569.         if ((cline=(char *)[headerInfo infoForKey:LINES]) != NULL) {
  570.             // article header has "Lines:", so set mark to cell
  571.             image = [self getArticleMarkFor:(int)atoi(cline)];
  572.             [node setImageForCell:image];
  573.         }
  574.         [node setLinkedData:articleItem nodeType:Header];
  575.                 newsgroupName = [knode dataGroupName];
  576.                 [node setDataGroupName:newsgroupName];
  577.         [node setLeaf:YES];
  578.         /* check article is already read or not */
  579.         //strncpy(groupnameTmp, newsgroupName, sizeof(groupnameTmp)-1);
  580.         groupnameTmp = NXCopyStringBuffer(newsgroupName);
  581.         makeTreeKey(groupnameTmp, ikey, ".");
  582.         groupInfo=[[iNewsGroupTreeRoot nodeForKey:ikey] 
  583.                             dataForKey:GROUPINFO];
  584.         free(groupnameTmp);
  585.         if ([groupInfo checkArticleIsRead:
  586.                 (int)[headerInfo infoForKey:ARTICLE_NUM]]) {
  587.             [node setActive:NO];
  588.         } else {
  589.             [node setActive:YES];
  590.         }
  591.         }
  592.     }
  593.     }
  594.     return listnode;
  595. }
  596.  
  597. - _itemHeadersOf:knode
  598. {
  599.     id        newsGroup;
  600.     id        listnode;
  601.     id        node;
  602.     id        article;
  603.     id        headerInfo, groupInfo;
  604.     int        i;
  605.     const char    *newsgroupName;
  606. //    char    groupnameTmp[256];
  607.     char    *groupnameTmp;
  608.     char    *message_id;
  609.     id        image;
  610.     char    *cline;
  611.  
  612.     newsGroup = [knode linkedData];    
  613.     iArticleDB = [self initNewsGroup:newsGroup readFlag:(ReadFlag)irFlag];
  614.     
  615.     listnode = [[IOrderedListD allocFromZone:[self zone]]
  616.                         initWithKey:"listnode"];    
  617.     for (i=0; article=[iArticleDB objectAt:i]; ++i) {
  618.     headerInfo = [article dataForKey:HEADER_INFO];
  619.     message_id = [headerInfo infoForKey:MESSAGE_ID];
  620.     /* if no message_id, bring up alert panel and skip it */
  621.     if (message_id == NULL) {
  622.         NXRunAlertPanel(LoStr("NewsBase"),LoStr("no Message-ID field")
  623.         ,LoStr("OK"),NULL,NULL);
  624.         continue;
  625.     }
  626.     node = [[IUifNode allocFromZone:[self zone]] initWithKey:message_id];
  627.     [listnode addObject:node];
  628.     [node setTitleForCell:[headerInfo infoForKey:SUBJECT]];
  629.     if ((cline=(char *)[headerInfo infoForKey:LINES]) != NULL) {
  630.         // article header has "Lines:", so set mark to cell
  631.         image = [self getArticleMarkFor:(int)atoi(cline)];
  632.         [node setImageForCell:image];
  633.     }
  634.     [node setLinkedData:article nodeType:Header];
  635.         newsgroupName = [knode dataGroupName];
  636.         [node setDataGroupName:newsgroupName];
  637.         [node setLeaf:YES];
  638.     
  639.     /* check article is already read or not */
  640.     //strncpy(groupnameTmp, newsgroupName, sizeof(groupnameTmp)-1);
  641.     groupnameTmp = NXCopyStringBuffer(newsgroupName);
  642.     makeTreeKey(groupnameTmp, ikey, ".");
  643.     groupInfo=[[iNewsGroupTreeRoot nodeForKey:ikey] dataForKey:GROUPINFO];
  644.     free(groupnameTmp);
  645.     if ([groupInfo checkArticleIsRead:
  646.                 (int)[headerInfo infoForKey:ARTICLE_NUM]]) {
  647.         [node setActive:NO];
  648.     } else {
  649.         [node setActive:YES];
  650.     }
  651.     }
  652.  
  653.     return listnode;
  654. }
  655.  
  656. - getArticleMarkFor:(int)linenum
  657. {
  658.     id    image;
  659.     
  660.     if (linenum < iBoundaryForTwo) {
  661.     image = [NXImage findImageNamed:SIZEMARKONE];
  662.     } else if (iBoundaryForTwo <= linenum  && linenum < iBoundaryForThree) {
  663.     image = [NXImage findImageNamed:SIZEMARKTWO];
  664.     } else if (iBoundaryForThree <= linenum ) {
  665.     image = [NXImage findImageNamed:SIZEMARKTHREE];
  666.     } else {
  667.     // linenum <= 0, and other
  668.     image = NULL;
  669.     }
  670.     return image;
  671. }
  672.     
  673. - itemOf:knode
  674. {
  675.     IOrderedListD *article;
  676.     InfoD *header;
  677.     const char *messageId;
  678.     
  679.     if ((article = [knode linkedData]) == nil) {
  680.     return(nil);
  681.     }
  682.     if ((header = [article dataForKey:HEADER_INFO]) == nil) {
  683.         return(nil);
  684.     }
  685.     messageId = (const char *)[header infoForKey:MESSAGE_ID];
  686.     if ([self sendArticle:messageId] == YES) {
  687.         return(self);
  688.     } else {
  689.         return(nil);
  690.     }
  691. }
  692.  
  693. - (BOOL)toggleActive:knode
  694. {
  695.     id        newsGroup, groupInfo;
  696.     id        headerInfo;
  697.     int        art_num;
  698.     const char    *newsgroupName;
  699.     char    *groupnameTmp;
  700.     enum {ACTIVATE, NOTACTIVATE} activateAction;
  701.     char    *xref, buf[MAXPATHLEN], *bufptr[MAX_NO_OF_TOKENS];
  702.     char    groupNameBuf[MAXPATHLEN];
  703.     int        i;
  704.     NXTreeState    state;
  705.     id        rootnode, tnode;
  706.  
  707.     DBG(1, fprintf(stderr,"INntpIO: -- toggleActive fired\n"));
  708.     
  709.     switch ([knode nodeType]) {
  710.     /* message is coming from newsgroup browser */
  711.     /* toggle subscribe */
  712.     case DirOfItems:
  713.     // selected cell is a newsgroup leaf cell
  714.     DBG(10,fprintf(stderr,"-- toggle SUBSCRIBE"));
  715.     
  716.     groupInfo = [[knode linkedData] dataForKey:GROUPINFO];
  717.     if ([knode active]) {
  718.         /* knode is active, subscribe -> unsubscribe */
  719.         [groupInfo addInfoString:"no" key:SUBSCRIBE];
  720.     } else {
  721.         /* knode is not active, unsubscribe -> subscribe */
  722.         [groupInfo addInfoString:"yes" key:SUBSCRIBE];
  723.     }
  724.     return YES;
  725.     break;
  726.     
  727.     case DirOfSubDirs:
  728.     // selected cell is a directory of newsgroup
  729.     // subscribe or unsubscribe all newsgroup under this directory
  730.  
  731.     activateAction = ([knode active]) ? NOTACTIVATE : ACTIVATE;
  732.  
  733.     rootnode = [knode linkedData];
  734.     [rootnode initState:&state];
  735.     while (tnode=[rootnode nextState:&state]) {
  736.         if (strcmp([tnode key], GROUPINFO) == 0) {
  737.         // tnode is groupInfo for newsgroup 
  738.         if (activateAction == NOTACTIVATE) {
  739.             [tnode addInfoString:"no" key:SUBSCRIBE];
  740.         } else {
  741.             [tnode addInfoString:"yes" key:SUBSCRIBE];
  742.         }
  743.         }
  744.     }
  745.     return YES;
  746.     break;
  747.     
  748.     /* message is coming from article header browser */
  749.     /* toggle article mark */
  750.     case ReferenceGroup:
  751.         // selected cell is a directory of article
  752.     // knode is obj. of "ITreeNodeD"
  753.     activateAction = ([knode active]) ? NOTACTIVATE : ACTIVATE;
  754.  
  755.     rootnode = [knode linkedData];
  756.     [rootnode initState:&state];
  757.     while (tnode=[rootnode nextState:&state]) {
  758.         if ([tnode isMemberOf:[IOrderedListD class]] &&
  759.             ((headerInfo=[tnode dataForKey:HEADER_INFO]) != nil)) {
  760.         // tnode is article
  761.         art_num = (int)[headerInfo infoForKey:ARTICLE_NUM];
  762.         
  763.         groupnameTmp = NXCopyStringBuffer([knode dataGroupName]);
  764.         makeTreeKey(groupnameTmp, ikey, ".");
  765.         newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey];
  766.         free(groupnameTmp);
  767.         groupInfo = [newsGroup dataForKey:GROUPINFO];
  768.         
  769.         if (activateAction == NOTACTIVATE) {
  770.             // this directory cell is active so mark all article 
  771.             // as read
  772.             [groupInfo markReadArticle:art_num];
  773.         } else {
  774.             [groupInfo unmarkReadArticle:art_num];
  775.         }
  776.         }
  777.     }
  778.     return YES;
  779.     break;
  780.     
  781.     case SubjectGroup:
  782.     case KeywordGroup:
  783.     // selected cell is a directory of article
  784.     // knode is obj. of "IOrderedListD"
  785.     
  786.     activateAction = ([knode active]) ? NOTACTIVATE : ACTIVATE;
  787.     rootnode = [knode linkedData];
  788.     for (i=0; tnode=[rootnode objectAt:i]; ++i) {
  789.         if ([tnode isMemberOf:[IOrderedListD class]] &&
  790.             ((headerInfo=[tnode dataForKey:HEADER_INFO]) != nil)) {
  791.         // tnode is article
  792.         art_num = (int)[headerInfo infoForKey:ARTICLE_NUM];
  793.         
  794.         groupnameTmp = NXCopyStringBuffer([knode dataGroupName]);
  795.         makeTreeKey(groupnameTmp, ikey, ".");
  796.         newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey];
  797.         free(groupnameTmp);
  798.         groupInfo = [newsGroup dataForKey:GROUPINFO];
  799.         
  800.         if (activateAction == NOTACTIVATE) {
  801.             // selected cell is active
  802.             // mark all article as read under this directory
  803.             [groupInfo markReadArticle:art_num];
  804.         } else {
  805.             [groupInfo unmarkReadArticle:art_num];
  806.         }
  807.         }
  808.     }
  809.     return YES;
  810.     break;
  811.     
  812.     case Header:
  813.     // selected cell is a leaf cell of article    
  814.     DBG(1,fprintf(stderr,"-- toggle article mark"));
  815.     
  816.     headerInfo = [[knode linkedData] dataForKey:HEADER_INFO];
  817.     // mark this article first
  818.     newsgroupName = [knode dataGroupName];
  819.     groupnameTmp = NXCopyStringBuffer(newsgroupName);
  820.     makeTreeKey(groupnameTmp, ikey, ".");
  821.     newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey];
  822.     free(groupnameTmp);
  823.  
  824.     groupInfo = [newsGroup dataForKey:GROUPINFO];
  825.  
  826.     // set this article's article number and mark or unmark it
  827.     // activateAction will be used for Xref: things
  828.     art_num = (int)[headerInfo infoForKey:ARTICLE_NUM];
  829.     if ([groupInfo checkArticleIsRead:art_num]) {
  830.         /* article is already read, mark -> unmark */
  831.         [groupInfo unmarkReadArticle:art_num];
  832.         activateAction = NOTACTIVATE;
  833.     } else {
  834.         [groupInfo markReadArticle:art_num];
  835.         activateAction = ACTIVATE;
  836.     }
  837.     
  838.     // do Xref: things
  839.     if ((xref=(char *)[headerInfo infoForKey:"Xref"]) == NULL) {
  840.         // this article does not have cross reference
  841.         return YES;
  842.     } else {
  843.         // article has Xref:
  844.         strncpy(buf, xref, sizeof(buf));
  845.         makeTreeKey(buf, bufptr, " ");
  846.         
  847.         for (i=1; bufptr[i] != NULL; ++i) {
  848.         // skip first one (xxxxx)
  849.         //   assume that Xref: field is like
  850.         //        xxxxx aaa.aaa.aaa:nnn bbb.bbb.bbb:mmm .....
  851.         //        xxxxx: e.g. ISRNEWS
  852.         //        aaa.aaa.aaa, bbb.bbb.bbb: newsgroup
  853.         //        nnn, mmm: article number
  854.         
  855.         if (sscanf(bufptr[i],"%1023[^:]:%d",groupNameBuf,&art_num)==2){
  856.             // buf is newsgroup name
  857.             DBG(1, fprintf(stderr,"newsgroup=%s\t art_num=%d\n",
  858.                             groupNameBuf, art_num));
  859.             makeTreeKey(groupNameBuf, ikey, ".");
  860.             newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey];
  861.             groupInfo = [newsGroup dataForKey:GROUPINFO];
  862.             if (activateAction == NOTACTIVATE) {
  863.             [groupInfo unmarkReadArticle:art_num];
  864.             } else {
  865.             [groupInfo markReadArticle:art_num];
  866.             }
  867.         }
  868.         }
  869.         return YES;
  870.     }
  871.     break;
  872.     default:
  873.     // should never coming here
  874.     return NO;
  875.     break;
  876.     }
  877.     return NO;
  878. }
  879.  
  880. - (BrowserMode)toggleNewsGroupMode:sender
  881. {
  882.     switch ((BrowserMode)[[sender selectedCell] tag]) {
  883.     case Flat:
  884.     iNewsGroupMode = Flat;
  885.     break;
  886.     case Tree:
  887.     default:
  888.     iNewsGroupMode = Tree;
  889.     break;
  890.     }
  891.     return iNewsGroupMode;
  892. }
  893.  
  894. - (BrowserMode)toggleArticleMode:sender
  895. {
  896.     switch ((BrowserMode)[[sender selectedCell] tag]) {
  897.     case Flat:
  898.     iArticleMode = Flat;
  899.         break;
  900.     case Tree:
  901.     iArticleMode = Tree;
  902.         break;
  903.     case BySubject:
  904.     iArticleMode = BySubject;
  905.         break;
  906.     case ByKeyword:
  907.     iArticleMode = ByKeyword;
  908.         break;
  909.     }
  910.     return(iArticleMode);
  911. }
  912.  
  913. - setArticleMode:(BrowserMode)articleMode
  914. {
  915.     iArticleMode = articleMode;
  916.     return(self);
  917. }
  918.  
  919. - setReadFlag:(ReadFlag)rflag
  920. {
  921.     irFlag = rflag;
  922.     return self;
  923. }
  924.  
  925. - _makeArticleTree
  926. {
  927.     int        i;
  928.     id        articleItem;
  929.  
  930.     iArticleHashTable = [[HashTable allocFromZone:headersZone] 
  931.                         initKeyDesc:"*" valueDesc:"@"];
  932.     iArticleReferenceTreeRoot = [[ITreeNodeD allocFromZone:headersZone] 
  933.                                 initWithKey:"root"];
  934.     
  935.     for (i=0; articleItem=[iArticleDB objectAt:i]; ++i) {
  936.         [iArticleHashTable insertKey:[[articleItem dataForKey:HEADER_INFO]
  937.                     infoForKey:MESSAGE_ID] value:articleItem];
  938.     }
  939.  
  940.     [self _addArticleToTree];
  941.     return iArticleReferenceTreeRoot;
  942. }
  943.  
  944. - (void)_addArticleToTree
  945. {
  946. #define TREELISTSIZE 128
  947.     char    treeKeyList[TREELISTSIZE];
  948.     char    treeKeyListTmp[TREELISTSIZE];
  949.     const char     *mes_id;
  950.     id        node;
  951.     NXHashState    state;
  952.     id        articleItem;
  953.     id        org_article;
  954.     char    *org_art_messageID;
  955.     int        org_art_num;
  956.     char    *key[MAX_NO_OF_TOKENS];
  957.  
  958.     state = [iArticleHashTable initState];
  959.     
  960.     while ([iArticleHashTable nextState:&state 
  961.             key:(const void **)&mes_id value:(void **)&articleItem]) {
  962.     /* look for original article's message_id */
  963.     if ((org_art_messageID=[self findOriginalArticle:articleItem])==NULL) {
  964.         /* original article */
  965.         sprintf(treeKeyList, "%010d", 
  966.           (int)[[articleItem dataForKey:HEADER_INFO] infoForKey:ARTICLE_NUM]);
  967.     } else {
  968.         /* article has reference */
  969.         if ((org_article = [iArticleHashTable 
  970.                 valueForKey:(char *)org_art_messageID]) == NULL) {
  971.         /* if original aritlce is not in server, 
  972.                 key = "0000000000_messageID 0000000yyy" */
  973.         sprintf(treeKeyList, "0000000000_%.105s %010d",
  974.             (char *)org_art_messageID, 
  975.             (int)[[articleItem dataForKey:HEADER_INFO] 
  976.                           infoForKey:ARTICLE_NUM]);
  977.         } else {
  978.         /* if original article is in server, 
  979.                         key= "0000000xxx 0000000yyy" */
  980.         org_art_num = (int)[[org_article dataForKey:HEADER_INFO]
  981.                             infoForKey:ARTICLE_NUM];
  982.         sprintf(treeKeyList, "%010d %010d", org_art_num,
  983.                       (int)[[articleItem dataForKey:HEADER_INFO] 
  984.                   infoForKey:ARTICLE_NUM]);
  985.         }
  986.         DBG(10,fprintf(stderr,"treeKeyList = %s\n org_subject = %s\n"
  987.             "Subject = %s", treeKeyList, 
  988.        (char *)[[org_article dataForKey:HEADER_INFO] infoForKey:SUBJECT],
  989.        (char *)[[articleItem dataForKey:HEADER_INFO] infoForKey:SUBJECT]));
  990.     }
  991.     strncpy(treeKeyListTmp, treeKeyList, sizeof(treeKeyListTmp)-1);
  992.     makeTreeKey(treeKeyListTmp, key, " ");
  993.         node = [iArticleReferenceTreeRoot addNodeForKey:key];
  994.     [node insertKeyedObject:articleItem];
  995.     }
  996. }
  997.  
  998. - (char *)findOriginalArticle:articleItem
  999. {
  1000.     char    *headRef, *references;
  1001.     char    *ch;
  1002.     
  1003.     if (([[articleItem dataForKey:HEADER_INFO] 
  1004.                         infoForKey:REFERENCES]) == NULL) {
  1005.     /* if not responsed article */
  1006.     return NULL;
  1007.     }
  1008.  
  1009.     do {
  1010.     if((references=[[articleItem dataForKey:HEADER_INFO] 
  1011.                 infoForKey:REFERENCES]) == NULL) {
  1012.         return headRef;
  1013.     }
  1014.     headRef = references;
  1015.         if ((ch=strchr(headRef,' ')) != NULL) {
  1016.         *ch = '\0';
  1017.     } else {
  1018.         /* if not ' ' in headRef, only one message id
  1019.            is in references field , and that's original article */
  1020.         return headRef;
  1021.     }
  1022.     } while (articleItem=[iArticleHashTable valueForKey:headRef]);
  1023.     
  1024.     return headRef;
  1025. }
  1026.  
  1027. - (BrowserMode)newsGroupMode
  1028. {
  1029.     return iNewsGroupMode;
  1030. }
  1031.  
  1032. - (BrowserMode)articleMode
  1033. {
  1034.     return(iArticleMode);
  1035. }
  1036.  
  1037. - (IOrderedListD *)_subjectHeadersOf:(IUifNode *)newsgroupNode
  1038. {
  1039.     int         i, k;
  1040.     IOrderedListD    *articleItem;
  1041.     InfoD        *header;
  1042.     const char        *subject;
  1043.     int            articleNo;
  1044.     char        articleNoString[16];
  1045.     IOrderedListD    *listnode;
  1046.     HashTable        *tableOfSubjectGroups;
  1047.     NXHashState     tableState;
  1048.     IOrderedListD    *subjectGroup;
  1049.     IUifNode        *subjectNode;
  1050.     IUifNode        *articleNode;
  1051.     const char        *newsgroupName;
  1052.     char        *groupnameTmp;
  1053.     char        *cline;
  1054.     id            image;
  1055.     BOOL        isUnreadArticle;
  1056.     INewsgroupInfoD    *groupInfo;
  1057.     id            c_article, c_header;
  1058.  
  1059.     iArticleDB = [self initNewsGroup:[newsgroupNode linkedData]
  1060.         readFlag:(ReadFlag)irFlag];
  1061.     // use a temporary hashtable to map subjects to subject groups
  1062.     tableOfSubjectGroups = [[HashTable allocFromZone:headersZone]
  1063.         initKeyDesc:"*" valueDesc:"@"];
  1064.     for (i = 0; (articleItem = [iArticleDB objectAt:i]) != nil; ++i) {
  1065.         header = [articleItem objectWithKey:HEADER_INFO];
  1066.         subject = [header infoForKey:SUBJECT];
  1067.         // strip Re: from subject if present
  1068.         if (strncmp(subject, "Re:", 3) == 0 || strncmp(subject, "re:", 3)
  1069.             == 0 || strncmp(subject, "RE:", 3) == 0) {
  1070.             subject += 3;
  1071.         }
  1072.         if (subject[0] == ' ') {
  1073.             ++subject;
  1074.         }
  1075.         articleNo = (int)[header infoForKey:ARTICLE_NUM];
  1076.         sprintf(articleNoString, "%010d", articleNo);
  1077.         if ((subjectGroup = [tableOfSubjectGroups valueForKey:subject])
  1078.             == nil) {
  1079.             // create a subject group for this subject
  1080.             subjectGroup = [[IOrderedListD allocFromZone:headersZone]
  1081.                 initWithKey:subject];
  1082.             [tableOfSubjectGroups insertKey:[subjectGroup key]
  1083.                 value:subjectGroup];
  1084.         }
  1085.         // add article to this subject group
  1086.         [subjectGroup insertKeyedObject:(IKeyedObject *)articleItem];
  1087.     }
  1088.  
  1089.     newsgroupName = [newsgroupNode dataGroupName];
  1090.     groupnameTmp = NXCopyStringBuffer(newsgroupName);
  1091.     makeTreeKey(groupnameTmp, ikey, ".");
  1092.     groupInfo = [[iNewsGroupTreeRoot nodeForKey:ikey] dataForKey:GROUPINFO];
  1093.     free (groupnameTmp);
  1094.     
  1095.     tableState = [tableOfSubjectGroups initState];
  1096.     listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:""];
  1097.     while ([tableOfSubjectGroups nextState:&tableState
  1098.         key:(const void **)&subject value:(void **)&subjectGroup]) {
  1099.         articleItem = [subjectGroup objectAt:0];
  1100.         header = [articleItem objectWithKey:HEADER_INFO];
  1101.         articleNo = (int)[header infoForKey:ARTICLE_NUM];
  1102.         sprintf(articleNoString, "%010d", articleNo);
  1103.         if ([subjectGroup count] == 1) {
  1104.             // create articleNode and link to article
  1105.             articleNode = [[IUifNode allocFromZone:[self zone]]
  1106.                 initWithKey:articleNoString];
  1107.             [articleNode setLinkedData:articleItem nodeType:Header];
  1108.             // use subject as title
  1109.             [articleNode setTitleForCell:[header infoForKey:SUBJECT]];
  1110.         // set image for line num
  1111.         if ((cline=(char *)[header infoForKey:LINES]) != NULL) {
  1112.             // article header has "Lines:", so set mark to cell
  1113.             image = [self getArticleMarkFor:(int)atoi(cline)];
  1114.             [articleNode setImageForCell:image];
  1115.         }
  1116.             [articleNode setDataGroupName:newsgroupName];
  1117.             [articleNode setLeaf:YES];
  1118.             if ([groupInfo checkArticleIsRead:articleNo] == YES) {
  1119.                 [articleNode setActive:NO];
  1120.             } else {
  1121.                 [articleNode setActive:YES];
  1122.             }
  1123.             [listnode insertKeyedObject:articleNode];
  1124.             [subjectGroup removeObject:articleItem];
  1125.             [subjectGroup free];
  1126.         } else {
  1127.             // More than one article in this subject group so create a subject
  1128.             // node for this subject and link to subject group
  1129.             subjectNode = [[IUifNode allocFromZone:[self zone]]
  1130.                 initWithKey:articleNoString];
  1131.             [subjectNode setLinkedData:subjectGroup nodeType:SubjectGroup];
  1132.             [subjectNode setTitleForCell:subject];
  1133.             [subjectNode setDataGroupName:newsgroupName];
  1134.         [subjectNode setLeaf:NO];
  1135.         
  1136.         // check if there is unread article 
  1137.         isUnreadArticle = NO;
  1138.         for (k=0; c_article=[subjectGroup objectAt:k]; ++k) {
  1139.         if ([c_article isMemberOf:[IOrderedListD class]] &&
  1140.             ((c_header=[c_article dataForKey:HEADER_INFO]) != nil)) {
  1141.             if ([groupInfo checkArticleIsRead: 
  1142.             (int)[c_header infoForKey:ARTICLE_NUM]] == NO) {
  1143.             // there is an unread article 
  1144.             isUnreadArticle = YES;
  1145.             break;
  1146.             }
  1147.         }
  1148.         }
  1149.         if (isUnreadArticle == YES) {
  1150.         [subjectNode setActive:YES];
  1151.         } else {
  1152.         [subjectNode setActive:NO];
  1153.         }
  1154.             [listnode insertKeyedObject:subjectNode];
  1155.         }
  1156.     }
  1157.     [tableOfSubjectGroups free];
  1158.     return(listnode);
  1159. }
  1160.  
  1161. - _itemHeadersOfSubject:(IUifNode *)subjectNode
  1162. {
  1163.     IOrderedListD       *subjectGroup;
  1164.     int                 i;
  1165.     IOrderedListD       *articleItem;
  1166.     InfoD               *header;
  1167.     int                 articleNo;
  1168.     char                articleNoString[16];
  1169.     IOrderedListD       *listnode;
  1170.     IUifNode            *articleNode;
  1171.     const char          *newsgroupName;
  1172. //    char        groupnameTmp[256];
  1173.     char        *groupnameTmp;
  1174.     char        *cline;
  1175.     id            image;
  1176.  
  1177.     subjectGroup = [subjectNode linkedData];
  1178.     listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:""];
  1179.     for (i = 0; (articleItem = [subjectGroup objectAt:i]) != nil; ++i) {
  1180.         header = [articleItem objectWithKey:HEADER_INFO];
  1181.         articleNo = (int)[header infoForKey:ARTICLE_NUM];
  1182.         sprintf(articleNoString, "%010d", articleNo);
  1183.         // create articleNode and link to article
  1184.         articleNode = [[IUifNode allocFromZone:[self zone]]
  1185.             initWithKey:articleNoString];
  1186.         [articleNode setLinkedData:articleItem nodeType:Header];
  1187.         // use sender as title
  1188.         if ([subjectNode nodeType] == KeywordGroup) {
  1189.             [articleNode setTitleForCell:[header infoForKey:SUBJECT]];
  1190.         } else {
  1191.             [articleNode setTitleForCell:[header infoForKey:FROM]];
  1192.         }
  1193.     // set image by line num
  1194.     if ((cline=(char *)[header infoForKey:LINES]) != NULL) {
  1195.         // article header has "Lines:", so set mark to cell
  1196.         image = [self getArticleMarkFor:(int)atoi(cline)];
  1197.         [articleNode setImageForCell:image];
  1198.     }
  1199.         newsgroupName = [subjectNode dataGroupName];
  1200.         [articleNode setDataGroupName:newsgroupName];
  1201.         [articleNode setLeaf:YES];
  1202.         // add article node to subject group
  1203.     //strncpy(groupnameTmp, newsgroupName, sizeof(groupnameTmp)-1);
  1204.     groupnameTmp = NXCopyStringBuffer(newsgroupName);
  1205.         makeTreeKey(groupnameTmp, ikey, ".");
  1206.         if ([[[iNewsGroupTreeRoot nodeForKey:ikey] dataForKey:GROUPINFO]
  1207.             checkArticleIsRead:articleNo] == YES) {
  1208.             [articleNode setActive:NO];
  1209.         } else {
  1210.             [articleNode setActive:YES];
  1211.             // also make the subject group active
  1212.             [subjectNode setActive:YES];
  1213.         }
  1214.     free(groupnameTmp);
  1215.         [listnode insertKeyedObject:articleNode];
  1216.     }
  1217.     return(listnode);
  1218. }
  1219.  
  1220. #define KEYWORD_LIST_SIZE 4096
  1221.  
  1222. - (IOrderedListD *)_keywordHeadersOf:(IUifNode *)newsgroupNode
  1223. {
  1224.     const char        *newsgroupName;
  1225.     int         i;
  1226.     IOrderedListD    *articleItem;
  1227.     InfoD        *header;
  1228.     const char         *keywords;
  1229.     char        keywordList[KEYWORD_LIST_SIZE], *ptr;
  1230.     const char         *keyword;
  1231.     HashTable        *tableOfKeywordGroups;
  1232.     IOrderedListD    *keywordGroup;
  1233.     IOrderedListD    *listnode;
  1234.     IUifNode            *keywordNode;
  1235.  
  1236.     iArticleDB = [self initNewsGroup:[newsgroupNode linkedData]
  1237.         readFlag:(ReadFlag)irFlag];
  1238.     newsgroupName = [newsgroupNode dataGroupName];
  1239.     // use a temporary hashtable to map subjects to subject groups
  1240.     tableOfKeywordGroups = [[HashTable allocFromZone:headersZone]
  1241.         initKeyDesc:"*" valueDesc:"@"];
  1242.     listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:""];
  1243.     for (i = 0; (articleItem = [iArticleDB objectAt:i]) != nil; ++i) {
  1244.         header = [articleItem objectWithKey:HEADER_INFO];
  1245.         strncpy(keywordList, [header infoForKey:SUBJECT], sizeof(keywordList));
  1246.         keywordList[sizeof(keywordList) - 1] = '\0';
  1247.         if ((keywords = [header infoForKey:KEYWORDS]) != NULL) {
  1248.             ptr = index(keywordList, '\0');
  1249.             *ptr++ = ' ';
  1250.             strncpy(ptr, keywords, &keywordList[sizeof(keywordList)]
  1251.                 - ptr);
  1252.             keywordList[sizeof(keywordList) - 1] = '\0';
  1253.         }
  1254.         for (ptr = keywordList; *ptr != '\0'; ++ptr) {
  1255.             if (isalnum(*ptr) == 0) {
  1256.                 *ptr = ' ';
  1257.             } else if (isupper(*ptr) != 0) {
  1258.                 *ptr = tolower(*ptr);
  1259.             }
  1260.         }
  1261.         for (keyword = strtok(keywordList, " "); keyword != NULL;
  1262.             keyword = strtok(NULL, " ")) {
  1263.             if (keyword[1] == '\0') {
  1264.                 continue;
  1265.             }
  1266.             if ([tableOfNoiseWords isKey:keyword] == YES) {
  1267.                 continue;
  1268.             }
  1269.             if ((keywordGroup = [tableOfKeywordGroups valueForKey:keyword])
  1270.                 == nil) {
  1271.                 // create a keyword group for this keyword
  1272.                 keywordGroup = [[IOrderedListD allocFromZone:headersZone]
  1273.                     initWithKey:keyword];
  1274.                 [tableOfKeywordGroups insertKey:[keywordGroup key]
  1275.                     value:keywordGroup];
  1276.                 keywordNode = [[IUifNode allocFromZone:[self zone]]
  1277.                     initWithKey:keyword];
  1278.                 [keywordNode setLinkedData:keywordGroup nodeType:KeywordGroup];
  1279.                 [keywordNode setTitleForCell:keyword];
  1280.                 [keywordNode setDataGroupName:newsgroupName];
  1281.                 [keywordNode setActive:YES];
  1282.                 [listnode insertKeyedObject:keywordNode];
  1283.             }
  1284.             // add article to this keyword group
  1285.             [keywordGroup insertKeyedObject:(IKeyedObject *)articleItem];
  1286.         }
  1287.     }
  1288.     [tableOfKeywordGroups free];
  1289.     return(listnode);
  1290. }
  1291.  
  1292. @end
  1293.  
  1294.